package drds.global_transaction.transaction_coordinator.transaction_session;

import drds.global_transaction.api.api.BranchTransactionStatus;
import drds.global_transaction.api.api.GlobalTransactionStatus;
import drds.global_transaction.api.exception.GlobalTransactionException;
import drds.global_transaction.transaction_coordinator.TransactionIds;
import drds.global_transaction.transaction_coordinator.store.SessionStorable;
import lombok.Getter;
import lombok.Setter;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class GlobalTransactionSession implements IGlobalTransactionSessionLifecycle, SessionStorable
{
	@Setter
	@Getter
	private long globalTransactionId;
	@Setter
	@Getter
	private GlobalTransactionStatus globalTransactionStatus;
	@Setter
	@Getter
	private String applicationId;
	@Setter
	@Getter
	private String transactionServiceGroup;
	@Setter
	@Getter
	private String transactionName;
	@Setter
	@Getter
	private int timeout;
	@Setter
	@Getter
	private long beginDataTimeLong;
	@Setter
	@Getter
	private boolean active;
	@Setter
	@Getter
	private List<BranchTransactionSession> branchTransactionSessionList = new ArrayList<>();
	@Setter
	@Getter
	private ArrayList<TransactionSessionLifecycleListener> transactionSessionLifecycleListenerList = new ArrayList<>();

	public GlobalTransactionSession()
	{
	}

	public GlobalTransactionSession(String applicationId, String transactionServiceGroup, String transactionName, int timeout)
	{
		this.globalTransactionId = TransactionIds.generateTransactionId();
		this.globalTransactionStatus = GlobalTransactionStatus.begin;

		this.applicationId = applicationId;
		this.transactionServiceGroup = transactionServiceGroup;
		this.transactionName = transactionName;
		this.timeout = timeout;
	}

	//
	@Override
	public void addBranchTransactionSession(BranchTransactionSession branchTransactionSession) throws GlobalTransactionException
	{
		for (TransactionSessionLifecycleListener transactionSessionLifecycleListener : transactionSessionLifecycleListenerList)
		{
			transactionSessionLifecycleListener.onAddBranchTransactionSession(this, branchTransactionSession);
		}
		branchTransactionSession.setBranchTransactionStatus(BranchTransactionStatus.registered_to_transaction_coordinator);
		branchTransactionSessionList.add(branchTransactionSession);
	}

	public void addSessionLifecycleListener(TransactionSessionLifecycleListener transactionSessionLifecycleListener)
	{
		transactionSessionLifecycleListenerList.add(transactionSessionLifecycleListener);
	}

	@Override
	public void begin() throws GlobalTransactionException
	{
		this.globalTransactionStatus = GlobalTransactionStatus.begin;
		this.beginDataTimeLong = System.currentTimeMillis();
		this.active = true;
		for (TransactionSessionLifecycleListener lifecycleListener : transactionSessionLifecycleListenerList)
		{
			lifecycleListener.onBeginGlobalTransactionSession(this);
		}
	}

	public boolean canBeAsynchronousCommitted()
	{

		return true;
	}

	@Override
	public void changeBranchTransactionStatus(BranchTransactionSession branchTransactionSession, BranchTransactionStatus branchTransactionStatus) throws GlobalTransactionException
	{
		for (TransactionSessionLifecycleListener transactionSessionLifecycleListener : transactionSessionLifecycleListenerList)
		{
			transactionSessionLifecycleListener.onBranchTransactionStatusChange(this, branchTransactionSession, branchTransactionStatus);
		}
		branchTransactionSession.setBranchTransactionStatus(branchTransactionStatus);
	}

	@Override
	public void changeGlobalTransactionStatus(GlobalTransactionStatus globalTransactionStatus) throws GlobalTransactionException
	{
		for (TransactionSessionLifecycleListener lifecycleListener : transactionSessionLifecycleListenerList)
		{
			lifecycleListener.onGlobalTransactionStatusChange(this, globalTransactionStatus);
		}
		this.globalTransactionStatus = globalTransactionStatus;

	}

	private void clean() throws GlobalTransactionException
	{
		for (BranchTransactionSession branchTransactionSession : branchTransactionSessionList)
		{
			branchTransactionSession.unlock();
		}

	}

	@Override
	public void close() throws GlobalTransactionException
	{
		if (active)
		{
			for (TransactionSessionLifecycleListener transactionSessionLifecycleListener : transactionSessionLifecycleListenerList)
			{
				transactionSessionLifecycleListener.onCloseGlobalTransactionSession(this);
			}
		}
	}

	public void closeAndClean() throws GlobalTransactionException
	{
		close();
		clean();

	}

	@Override
	public void decode(byte[] bytes)
	{
		ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
		this.globalTransactionId = byteBuffer.getLong();
		this.timeout = byteBuffer.getInt();
		short applicationIdLength = byteBuffer.getShort();
		if (applicationIdLength > 0)
		{
			byte[] $bytes = new byte[applicationIdLength];
			byteBuffer.get($bytes);
			this.applicationId = new String($bytes);
		}
		short transactionServiceGroupLength = byteBuffer.getShort();
		if (transactionServiceGroupLength > 0)
		{
			byte[] $bytes = new byte[transactionServiceGroupLength];
			byteBuffer.get($bytes);
			this.transactionServiceGroup = new String($bytes);
		}
		short transactionNameLength = byteBuffer.getShort();
		if (transactionNameLength > 0)
		{
			byte[] $bytes = new byte[transactionNameLength];
			byteBuffer.get($bytes);
			this.transactionName = new String($bytes);
		}
		this.beginDataTimeLong = byteBuffer.getLong();
	}

	@Override
	public byte[] encode()
	{
		ByteBuffer byteBuffer = ByteBuffer.allocate(512);
		byteBuffer.putLong(globalTransactionId);
		byteBuffer.putInt(timeout);
		if (null != applicationId)
		{
			byte[] bytes = applicationId.getBytes();
			byteBuffer.putShort((short) bytes.length);
			byteBuffer.put(bytes);
		} else
		{
			byteBuffer.putShort((short) 0);
		}
		if (null != transactionServiceGroup)
		{
			byte[] bytes = transactionServiceGroup.getBytes();
			byteBuffer.putShort((short) bytes.length);
			byteBuffer.put(bytes);
		} else
		{
			byteBuffer.putShort((short) 0);
		}
		if (null != transactionName)
		{
			byte[] bytes = transactionName.getBytes();
			byteBuffer.putShort((short) bytes.length);
			byteBuffer.put(bytes);
		} else
		{
			byteBuffer.putShort((short) 0);
		}
		byteBuffer.putLong(beginDataTimeLong);
		byteBuffer.flip();
		byte[] result = new byte[byteBuffer.limit()];
		byteBuffer.get(result);
		return result;
	}

	@Override
	public void end() throws GlobalTransactionException
	{
		for (TransactionSessionLifecycleListener transactionSessionLifecycleListener : transactionSessionLifecycleListenerList)
		{
			transactionSessionLifecycleListener.onEndGlobalTransactionSession(this);
		}

	}

	public BranchTransactionSession getBranchTransactionSession(long branchTransactionId)
	{
		synchronized (branchTransactionSessionList)
		{
			for (BranchTransactionSession branchTransactionSession : branchTransactionSessionList)
			{
				if (branchTransactionSession.getBranchTransactionId() == branchTransactionId)
				{
					return branchTransactionSession;
				}
			}

			return null;
		}

	}

	public List<BranchTransactionSession> getReverseSortedBranchTransactionSessionList()
	{
		List<BranchTransactionSession> list = new ArrayList<BranchTransactionSession>(branchTransactionSessionList);
		Collections.reverse(list);
		return list;
	}
	//

	public List<BranchTransactionSession> getSortedBranchTransactionSessionList()
	{
		return new ArrayList<BranchTransactionSession>(branchTransactionSessionList);
	}

	public boolean hasBranchTransaction()
	{
		return branchTransactionSessionList.size() > 0;
	}

	public boolean isTimeout()
	{
		return (System.currentTimeMillis() - beginDataTimeLong) > timeout;
	}

	public boolean removeBranchTransactionSession(BranchTransactionSession branchTransactionSession)
	{
		return branchTransactionSessionList.remove(branchTransactionSession);
	}

	public void removeSessionLifecycleListener(TransactionSessionLifecycleListener transactionSessionLifecycleListener)
	{
		transactionSessionLifecycleListenerList.remove(transactionSessionLifecycleListener);
	}

	//
	@Override
	public void unlockAndRemoveBranchTransactionSession(BranchTransactionSession branchTransactionSession) throws GlobalTransactionException
	{
		for (TransactionSessionLifecycleListener transactionSessionLifecycleListener : transactionSessionLifecycleListenerList)
		{
			transactionSessionLifecycleListener.onRemoveBranchTransactionSession(this, branchTransactionSession);
		}
		branchTransactionSession.unlock();
		removeBranchTransactionSession(branchTransactionSession);
	}
}
